﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Threading;
using System.Xml;
using System.Xml.XPath;

namespace Allegro.TaskExecution
{
    ///<summary>
    ///</summary>
    public class TaskCollection : Collection<ITask> //KeyedCollection<string, ITask>
    {
        private ITask _parentTask;
        private Dictionary<ITask, double> _weights;

        ///<summary>
        ///</summary>
        public TaskCollection()
        {
            
        }
        ///<summary>
        ///</summary>
        ///<param name="c"></param>
        public TaskCollection(TaskCollection c)
        {
            
        }
        internal TaskCollection(ITask parent)
        {
            _parentTask = parent;
            _weights = new Dictionary<ITask, double>();
        }
        internal TaskCollection(ITask parent, IEnumerable<ITask> tasks)
        {
            _parentTask = parent;
            _weights = new Dictionary<ITask, double>();
            if(tasks is TaskCollection)
            {
                TaskCollection collection = tasks as TaskCollection;
                foreach(ITask task in collection)
                {
                    _weights.Add(task, collection.GetEstimatedDuration(task));
                }
            }
            foreach (ITask task in tasks)
            {
                task.ParentTask = _parentTask;
                Add(task);
            }
        }
        //protected override string GetKeyForItem(ITask item)
        //{
        //    return item.Name;
        //}
        ///<summary>
        ///</summary>
        ///<param name="c"></param>
        ///<param name="p"></param>
        ///<returns></returns>
        public static TaskCollection operator +(TaskCollection c, ITask p)
        {
            c.Add(p,1.0);
            return c;
        }
        ///<summary>
        ///</summary>
        ///<param name="c"></param>
        ///<param name="p"></param>
        ///<returns></returns>
        public static TaskCollection operator -(TaskCollection c, ITask p)
        {
            c.Remove(p);
            return c;
        }
        ///<summary>
        ///</summary>
        ///<param name="c"></param>
        ///<returns></returns>
        public static implicit operator TaskCollection(ITask[] c)
        {
            return new TaskCollection(null, c);
        }
        ///<summary>
        ///</summary>
        public Dictionary<ITask, double> TaskWeights { get { return _weights; } }

        ///<summary>
        /// Add child task to the composite task
        ///</summary>
        ///<param name="item"></param>
        ///<param name="estimatedDuration"></param>
        public void Add(ITask item, double estimatedDuration)
        {
            Add(item);
            if(_weights.ContainsKey(item))
                _weights[item] = estimatedDuration;
            else
            {
                _weights.Add(item, estimatedDuration);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="childTask"></param>
        /// <returns></returns>
        public double GetEstimatedDuration(ITask childTask)
        {
            if (_weights.ContainsKey(childTask))
            {
                return _weights[childTask];
            }
            else
            {
                //string msg = string.Format("Task collection has no elapsed time estimate for child task duration{0}", childTask.Name);
                //Log.Error(msg, this, LogEventTypes.TaskEvent);
                //throw new ArgumentException(msg);
                return 1.0;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="item"></param>
        protected override void SetItem(int index, ITask item)
        {
            base.SetItem(index, item);
            if (!_weights.ContainsKey(item))
            {
                _weights.Add(item, 1.0);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        protected override void RemoveItem(int index)
        {
            if (index >= 0 && index < Count)
            {
                ITask task = this[index];
                base.RemoveItem(index);
                if (_weights.ContainsKey(task))
                    _weights.Remove(task);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="item"></param>
        protected override void InsertItem(int index, ITask item)
        {
            item.ParentTask = _parentTask;
            base.InsertItem(index, item);
        }
    }

    ///<summary>
    ///</summary>
    ///<param name="childTask"></param>
    ///<param name="currentActivityContext"></param>
    ///<param name="childActivityContext"></param>
    public delegate void PreProcessChildActivity(ITask childTask, IExecutionContext currentActivityContext, IExecutionContext childActivityContext);
    ///<summary>
    ///</summary>
    ///<param name="childTask"></param>
    ///<param name="currentActivityContext"></param>
    ///<param name="childActivityContext"></param>
    public delegate void PostProcessChildActivity(ITask childTask, IExecutionContext currentActivityContext, IExecutionContext childActivityContext);

    ///<summary>
    ///</summary>
    public abstract class CompositeTask : Task, ICompositeTask
    {
        ///<summary>
        ///</summary>
        public const string NumberOfCompletedChildTasks = "_nCompletedTasks_";

        //private IList<ITask> _tasks;
        private TaskCollection _tasks;
        //private Dictionary<ITask, double> _weights;
        //private ReadOnlyCollection<ITask> _readOnlyTasks;
        ///<summary>
        ///</summary>
        public PreProcessChildActivity PreProcessChildActivityDelegate { get; set; }
        ///<summary>
        ///</summary>
        public PostProcessChildActivity PostProcessChildActivityDelegate { get; set; }

        /// <summary>
        /// 
        /// </summary>
        protected CompositeTask()
        {
            //_tasks = new List<ITask>();
            _tasks = new TaskCollection(this);
            //_weights = new Dictionary<ITask, double>();
            //_readOnlyTasks = new ReadOnlyCollection<ITask>(_tasks);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        protected CompositeTask(string name)
            : base(name)
        {
            //_tasks = new List<ITask>();
            _tasks = new TaskCollection(this);
            //_weights = new Dictionary<ITask, double>();
            //_readOnlyTasks = new ReadOnlyCollection<ITask>(_tasks);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="preProcessChildActivityDelegate"></param>
        /// <param name="postProcessChildActivityDelegate"></param>
        protected CompositeTask(PreProcessChildActivity preProcessChildActivityDelegate, PostProcessChildActivity postProcessChildActivityDelegate)
        {
            //_tasks = new List<ITask>();
            _tasks = new TaskCollection(this);
            //_weights = new Dictionary<ITask, double>();
            PreProcessChildActivityDelegate = preProcessChildActivityDelegate;
            PostProcessChildActivityDelegate = postProcessChildActivityDelegate;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        ITask ICompositeItem<ITask>.this[string name]
        {
            get { throw new System.NotImplementedException(); }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="oid"></param>
        /// <returns></returns>
        ITask ICompositeItem<ITask>.this[Guid oid]
        {
            get { throw new System.NotImplementedException(); }
        }

        /// <summary>
        /// 
        /// </summary>
        public IList<ITask> Items
        {
            get { return _tasks; }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public ICompositeItem<ITask> Add(ITask item)
        {
            return AddTask(item);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public ICompositeTask AddTask(ITask item)
        {
            //_tasks.Add(item);
            //item.ParentTask = this;
            return AddTask(item, 1.0);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <param name="elapsedTimeEstimateInSeconds"></param>
        /// <returns></returns>
        public ICompositeTask AddTask(ITask item, double elapsedTimeEstimateInSeconds)
        {
            _tasks.Add(item, elapsedTimeEstimateInSeconds);
            item.ParentTask = this;
            //_weights.Add(item, elapsedTimeEstimateInSeconds);
            return this;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="childTask"></param>
        /// <returns></returns>
        public double GetEstimatedDuration(ITask childTask)
        {
            return _tasks.GetEstimatedDuration(childTask);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        public void Remove(ITask item)
        {
            if (_tasks.Contains(item))
                _tasks.Remove(item);
            //if (_weights.ContainsKey(item))
            //    _weights.Remove(item);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        public void Remove(string name)
        {
            ITask foundTask = null;
            foreach (ITask t in _tasks)
            {
                if (t.Name == name)
                    break;
            }
            if (foundTask != null)
            {
                _tasks.Remove(foundTask);
                //if (_weights.ContainsKey(foundTask))
                //    _weights.Remove(foundTask);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public TaskCollection Tasks
        {
            get { return _tasks; }
            set { _tasks = new TaskCollection(this, value); }
        }

        /// <summary>
        /// 
        /// </summary>
        protected IDictionary<ITask, double> TaskWeights { get { return _tasks.TaskWeights; } }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="persistenceContext"></param>
        protected override void SaveContent(XmlWriter writer, IXmlPersistenceContext persistenceContext)
        {
            base.SaveContent(writer, persistenceContext);

            writer.WriteStartElement("Tasks", XmlNamespace);
            {
                foreach (ITask task in Tasks)
                {
                    if (task is IXmlPersist)
                    {
                        writer.WriteStartElement("Task", XmlNamespace);
                        {
                            double weight = GetEstimatedDuration(task);
                            writer.WriteAttributeString("weight", weight.ToString(CultureInfo.InvariantCulture));
                            IXmlPersist taskPersist = task as IXmlPersist;
                            taskPersist.Save(writer, persistenceContext);
                        }
                        writer.WriteEndElement();
                    }
                }
            }
            writer.WriteEndElement();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="itemNode"></param>
        /// <param name="persistenceContext"></param>
        protected override void LoadContent(XPathNavigator itemNode, IXmlPersistenceContext persistenceContext)
        {
            base.LoadContent(itemNode, persistenceContext);

            XmlNamespaceManager nsMgr = persistenceContext.NamespaceManager;
            // Tasks
            XPathNavigator tasksRootNode = itemNode.SelectSingleNode("al:Tasks", nsMgr);
            if (tasksRootNode != null)
            {
                XPathNodeIterator tasks = tasksRootNode.Select("al:Task", nsMgr);
                foreach (XPathNavigator taskNode in tasks)
                {
                    string weightString = taskNode.GetAttribute("weight", "");
                    taskNode.MoveToFirstChild();
                    ITask t = XmlPersist.LoadObject<ITask>(taskNode);
                    AddTask(t, double.Parse(weightString, CultureInfo.InvariantCulture));
                }
            }

        }

        internal void ProgressUpdate(ITask task, IExecutionContext context, ProgressEventType eventType, double progress, string message)
        {
            PostEvent(task, context, eventType, progress, message);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="callback"></param>
        /// <param name="context"></param>
        public override void NotifyProgress(TaskExecutionProgressNotification callback, IExecutionContext context)
        {
            base.NotifyProgress(callback, context);

            // Subscribe for events from the child events
            Monitor.Enter(Tasks);
            var tasklist = new List<ITask>(Tasks);
            Monitor.Exit(Tasks);
            foreach (ITask t in tasklist)
            {
                t.NotifyProgress(ProgressUpdate, context);
            }
        }
    }
}
